/*********************************************************************************************//**
* \file cartesd.c
* \author Daneluzzi F., Pelloux-Prayer J., Battandier P.
* \version 1
* \date janvier 2012
* \brief Bibliothèque contenant les fonctions utilisées pour la gestion de la carte SD.
*************************************************************************************************/
#include "cartesd.h"

/*********************************************************************//**
 * \var sd_cmdtable
 * \brief Provides information for all the sdmmc commands that we support
 * \note We turn off the CRC as soon as possible, so the commands with
 * 0xFF don't need to be calculated in runtime
 *********************************************************************/
SD_sCMD sd_cmdtable[] =
{
    // cmd                      crc   response  argument
    {SD_CMD_GO_IDLE_STATE,      0x95, R1,       NODATA},
    {SD_CMD_SEND_OP_COND,       0xF9, R1,       NODATA},
    {SD_CMD_SEND_CSD,           0xAF, R1,       MOREDATA},
    {SD_CMD_SEND_CID,           0x1B, R1,       MOREDATA},
    {SD_CMD_SET_BLOCKLEN,       0xFF, R1,       NODATA},
    {SD_CMD_READ_SINGLE_BLOCK,  0xFF, R1,       MOREDATA},
    {SD_WRITE_SINGLE_BLOCK,     0xFF, R1,       MOREDATA},
};

/**** Variables globales ****/
SD_CSD gblCSDReg;

#pragma udata bufferSDLow
char bufferLow[256];

#pragma udata bufferSDHigh
char bufferHigh[256];

SD_STATUS sd_init()
{
    sd_init_host();
    return sd_init_card();
}

void sd_init_host(void)
{
    // Configuration des pins
    // SPI Clock => out
    SPI_SCK_AN = PIN_NUMERIQUE;
    SPI_SCK_DIR = PIN_SORTIE;

    // Slave Output Master Input => in
    SPI_MISO_AN = PIN_NUMERIQUE;
    SPI_MISO_DIR = PIN_ENTREE;

    // Master Output Slave Input => out
    SPI_MOSI_AN = PIN_NUMERIQUE;
    SPI_MOSI_DIR = PIN_SORTIE;

    // Chip Select => out
    SPI_CSb_AN = PIN_NUMERIQUE;
    SPI_CSb_DIR = PIN_SORTIE;
    SPI_CSb = 1;

    // Card Detect => in
    SD_CD_AN = PIN_NUMERIQUE;
    SD_CD_DIR = PIN_ENTREE;

    // Write Protect => in

    // On ouvre le SPI1
    OpenSPI1(SPI_FOSC_64, MODE_11, SMPMID);

    // On attend que l'initialisation soit complètement fait
    Delay10KTCYx(100);
}

SD_STATUS sd_init_card(void)
{
    SD_RESPONSE response;
    SD_STATUS status;
    unsigned int c;

    // Initialisation avec au moins 74 cycles d'horloge
    for (c = 0; c < 10; c++)
        WriteSPI1(0xFF);

    // A chaque communication on met CSb à 0 (chip select)
    SPI_CSb = 0;
    Delay10KTCYx(1);

    // Envoi de la commande "reset" pour rentrer en mode SPI (CSb=0)
    response = sd_write(GO_IDLE_STATE, 0x00);

    // Si la réponse de la carte SD ne correspond pas
    if (response.r1.value == SD_BAD_RESPONSE)
    {
        SPI_CSb = 1;
        return SD_STATUS_INIT_COM_FAILURE;
    }
    // Si la réponse est différente de IDLE
    if (response.r1.value != 0x01)
    {
        SPI_CSb = 1;
        return SD_STATUS_NOT_INIT_FAILURE;
    }

    /* On envoie la SEND_OP_COND (Card Initialization Process)
     * jusqu'à ce que la SD réponde OK
     * Passé un certain TimeOut, on sort*/
    c = 0xFFF;    // Environ 1 seconde d'attente
    do
    {
        response = sd_write(SEND_OP_COND, 0x00);
        c--;
    } while (response.r1.value != SD_OK_RESPONSE && c != 0);

    // Si il y a eu timeout
    if (c == 0)
    {
        SPI_CSb = 1;
        return SD_STATUS_INIT_TIMEOUT;
    }
    else
    {
        // Récupération du registre CSD avant d'augmenter la vitesse
        status = sd_get_csd((char*)&gblCSDReg);
        if (!status)
            OpenSPI1(SPI_FOSC_16, MODE_11, SMPMID);       //Augmentation de l'horloge (FOSC/16)
        else
        {
            status = SD_STATUS_TYPE_INVALID;
        }
    }
    // Définition des blocs de la carte SD
    response = sd_write(SET_BLOCKLEN, BLOCK_SIZE);

    SPI_CSb = 1;
    return status;
}

unsigned char sd_read(void)
{
    SSP1BUF = 0xFF;           //Data Out - Logic ones
    while(!SSP1STATbits.BF);  //Wait until cycle complete
    return(SSP1BUF);          //Return with byte read
}

SD_RESPONSE sd_write(unsigned char cmd, long arg)
{
    SD_CMD_PACKET command;
    SD_RESPONSE response;
    long timeout = 8;

    SPI_CSb = 0;

    command.cmd = sd_cmdtable[cmd].cmdCode;
    command.arg = arg;
    command.crc = sd_cmdtable[cmd].CRC;

    WriteSPI1(command.cmd);
    WriteSPI1(command.arg3);
    WriteSPI1(command.arg2);
    WriteSPI1(command.arg1);
    WriteSPI1(command.arg0);
    WriteSPI1(command.crc);

    switch (sd_cmdtable[cmd].responseType)
    {
        case R1:
        case R1b:
            do
            {
                response.r1.value = sd_read();
                timeout--;
            } while ((response.r1.value == 0xFF) && (timeout != 0));
            break;

        case R2:
            break;
    }

    WriteSPI1(0xFF);

    if(!(sd_cmdtable[cmd].moreDataExpected))
        SPI_CSb = 1;

    return response;
}

SD_STATUS sd_sector_read512(long sector_addr, char* bufferHigh, char* bufferLow)
{
    int index;
    SD_RESPONSE response;
    unsigned char data_token;
    SD_STATUS status = SD_STATUS_VALID;

    response = sd_write(READ_SINGLE_BLOCK, (sector_addr << 9));

    // Make sure the command was accepted
    if(response.r1.value != 0x00)
        status = SD_STATUS_BAD_CMD;
    else
    {
        index = 0x2FF;
        do
        {
            data_token = sd_read();
            index--;
        } while ((data_token == 0xFF) && (index != 0));

        if ((index == 0) || (data_token != SD_DATA_START_TOKEN))
            status = SD_STATUS_TIMEOUT;
        else
        {
            for (index = 0; index < BLOCK_SIZE/2; index++)
                *bufferHigh++ = sd_read();
            for (index = 0; index < BLOCK_SIZE/2; index++)
                *bufferLow++ = sd_read();
            WriteSPI1(0xFF);
            WriteSPI1(0xFF);
        }
        WriteSPI1(0xFF);    //Required clocking (see spec)
    }
    SPI_CSb = 1;
    return status;
}

SD_STATUS sd_sector_write512(long sector_addr, char* bufferHigh, char* bufferLow)
{
    unsigned char data_response;
    int index;
    SD_RESPONSE response;
    SD_STATUS status = SD_STATUS_VALID;

    response = sd_write(WRITE_SINGLE_BLOCK, (sector_addr << 9));

    // Make sure the command was accepted
    if(response.r1.value != 0x00)
        status = SD_STATUS_BAD_CMD;
    else
    {
        WriteSPI1(SD_DATA_START_TOKEN);                 //Send data start token

        for (index = 0; index < BLOCK_SIZE/2; index++)
            WriteSPI1(bufferHigh[index]);
        for (index = 0; index < BLOCK_SIZE/2; index++)
            WriteSPI1(bufferLow[index]);
        WriteSPI1(0xFF);                                // On envoie 2 octets de CRC
        WriteSPI1(0xFF);

        data_response = sd_read();                      //Read response
        if ((data_response & 0x0F) != DATA_ACCEPTED)
        {
            status = SD_STATUS_DATA_REJECTED;
        }
        else
        {
            index = 0xFFF; // using i as a timeout counter
            do
            {   // Wait for write completion
                data_response = sd_read();
                index--;
            } while ((data_response == 0x00) && (index != 0));

            if (index == 0) //if timeout first
                status = SD_STATUS_TIMEOUT;
        }
        WriteSPI1(0xFF);
    }
    SPI_CSb = 1;
    return (status);
}

unsigned char sd_detect(void)
{
    return (!SD_CD);
}

SD_STATUS sd_get_csd(char *buffer)
{
    unsigned char data_token;
    int index, timeout = 0x2ff;
    SD_RESPONSE response;
    SD_STATUS status = SD_STATUS_VALID;
    SD_CMD_PACKET CmdPacket;

    SPI_CSb = 0;

    CmdPacket.cmd = sd_cmdtable[SEND_CSD].cmdCode;
    CmdPacket.arg = 0x00;
    CmdPacket.crc = sd_cmdtable[SEND_CSD].CRC;

    WriteSPI1(CmdPacket.cmd);
    WriteSPI1(CmdPacket.arg3);
    WriteSPI1(CmdPacket.arg2);
    WriteSPI1(CmdPacket.arg1);
    WriteSPI1(CmdPacket.arg0);
    WriteSPI1(CmdPacket.crc);

    do
    {
        response.r1.value = sd_read();
        timeout--;
    } while ((response.r1.value == 0xFF) && (timeout != 0));

    // Make sure the command was accepted
    if(response.r1.value != 0x00)
    {
        status = SD_STATUS_BAD_CMD;
    }
    else
    {
        index = 0x2FF;

        // Now, must wait for the start token of data block
        do
        {
            data_token = sd_read();
            index--;
        } while ((data_token == 0xFF) && (index != 0));

        // Hopefully that zero is the datatoken
        if ((index == 0) || (data_token != SD_DATA_START_TOKEN))
            status = SD_STATUS_TIMEOUT;
        else
        {
            for(index = 0; index < 16; index++)
            {
                *buffer++ = sd_read();
            }
        }

        WriteSPI1(0xFF); // Required clocking (see spec)
    }

    SPI_CSb = 1;
    return status;
}

SD_STATUS sd_get_cid(char * buffer)
{
    unsigned char data_token;
    int index, timeout = 0x2ff;
    SD_RESPONSE response;
    SD_STATUS status = SD_STATUS_VALID;
    SD_CMD_PACKET CmdPacket;

    SPI_CSb = 0;

    CmdPacket.cmd = sd_cmdtable[SEND_CID].cmdCode;
    CmdPacket.arg = 0x00;
    CmdPacket.crc = sd_cmdtable[SEND_CID].CRC;

    // On envoie la commande
    WriteSPI1(CmdPacket.cmd);
    WriteSPI1(CmdPacket.arg3);
    WriteSPI1(CmdPacket.arg2);
    WriteSPI1(CmdPacket.arg1);
    WriteSPI1(CmdPacket.arg0);
    WriteSPI1(CmdPacket.crc);

    do
    {
        response.r1.value = sd_read();
        timeout--;
    } while ((response.r1.value == 0xFF) && (timeout != 0));

    // Make sure the command was accepted
    if(response.r1.value != 0x00)
    {
        status = SD_STATUS_BAD_CMD;
    }
    else
    {
        index = 0x2FF;
        // Now, must wait for the start token of data block
        do
        {
            data_token = sd_read();
            index--;
        } while ((data_token == 0xFF) && (index != 0));

        // Hopefully that zero is the datatoken
        if ((index == 0) || (data_token != SD_DATA_START_TOKEN))
            status = SD_STATUS_TIMEOUT;
        else
        {
            for(index = 0; index < 16; index++)
            {
                *buffer++ = sd_read();
            }
        }

        WriteSPI1(0xFF);        // Required clocking (see spec)
    }

    SPI_CSb = 1;
    return(status);
}

SD_STATUS add_measure(char buffer[], unsigned int bufferSize)
{
    unsigned char status;
    long sector_addr;
    int indexHigh, indexLow, cpt;
    read_pointer(&sector_addr, &indexHigh, &indexLow);

    status = sd_sector_read512(sector_addr, bufferHigh, bufferLow);

    for (cpt=0; cpt<bufferSize; cpt++)
    {
        if (indexHigh < 256)
        {
            bufferHigh[indexHigh++] = buffer[cpt];
        }
        else if (indexHigh >= 256)
        {
            bufferLow[indexLow++] = buffer[cpt];
        }
        else if (indexLow >= 256)
        {
            // Ecriture du bloc
            status = sd_sector_write512(sector_addr, bufferHigh, bufferLow);

            // Incrémente le pointeur et continue l'écriture du buffer
            sector_addr ++;
            indexHigh = 0;
            indexLow = 0;
            bufferHigh[indexHigh++] = buffer[cpt];
        }
    }

    status = sd_sector_write512(sector_addr, bufferHigh, bufferLow);
    if (indexHigh == 256 && indexLow == 256)
    {
        sector_addr ++;
        indexHigh = 0;
        indexLow = 0;
    }
    write_pointer(sector_addr, indexHigh, indexLow);
    return status;
}

SD_STATUS read_pointer(long *sectorPointer, int *bytePointerHigh, int *bytePointerLow)
{
    int index;
    SD_RESPONSE response;
    unsigned char data_token;
    SD_STATUS status = SD_STATUS_VALID;
    unsigned char pointer[POINTER_SIZE];

    response = sd_write(READ_SINGLE_BLOCK, POINTER_ADDR);
    // Make sure the command was accepted
    if(response.r1.value != 0x00)
        status = SD_STATUS_BAD_CMD;
    else
    {
        index = 0x2FF;
        do
        {
            data_token = sd_read();
            index--;
        } while ((data_token == 0xFF) && (index != 0));

        if ((index == 0) || (data_token != SD_DATA_START_TOKEN))
            status = SD_STATUS_TIMEOUT;
        else
        {
            for (index = 0; index < POINTER_SIZE; index++)
                pointer[index] = sd_read();
            for (index = POINTER_SIZE; index < BLOCK_SIZE; index++)
                sd_read();
            WriteSPI1(0xFF);
            WriteSPI1(0xFF);
        }
        WriteSPI1(0xFF); //Required clocking (see spec)
    }
    SPI_CSb = 1;

    *sectorPointer = (pointer[0]*512) + (pointer[1]*256) + pointer[2];
    *bytePointerHigh = (pointer[3]*256) + pointer[4];
    *bytePointerLow = (pointer[5]*256) + pointer[6];

    return status;
}

SD_STATUS write_pointer(long sectorPointer, int bytePointerHigh, int bytePointerLow)
{
    unsigned char data_response;
    int index;
    SD_RESPONSE response;
    SD_STATUS status = SD_STATUS_VALID;
    char pointer[POINTER_SIZE];

    pointer[0] = (char)(sectorPointer >> 16);
    pointer[1] = (char)(sectorPointer >> 8);
    pointer[2] = (char)(sectorPointer);
    pointer[3] = (char)(bytePointerHigh>>8);
    pointer[4] = (char) bytePointerHigh;
    pointer[5] = (char) (bytePointerLow>>8);
    pointer[6] = (char) bytePointerLow;

    response = sd_write(WRITE_SINGLE_BLOCK, (POINTER_ADDR << 9));

    // Make sure the command was accepted
    if(response.r1.value != 0x00)
        status = SD_STATUS_BAD_CMD;
    else
    {
        WriteSPI1(SD_DATA_START_TOKEN); //Send data start token

        for (index = 0; index < POINTER_SIZE; index++)
            WriteSPI1(pointer[index]);
        for (index = POINTER_SIZE; index < BLOCK_SIZE; index++)
            WriteSPI1(0x00);
        WriteSPI1(0xFF); // On envoie 2 octets de CRC
        WriteSPI1(0xFF);

        data_response = sd_read(); //Read response
        if ((data_response & 0x0F) != DATA_ACCEPTED)
        {
            status = SD_STATUS_DATA_REJECTED;
        }
        else
        {
            index = 0xFFF; // using i as a timeout counter
            do
            {   // Wait for write completion
                data_response = sd_read();
                index--;
            } while ((data_response == 0x00) && (index != 0));

            if (index == 0) //if timeout first
                status = SD_STATUS_TIMEOUT;
        }
        WriteSPI1(0xFF);
    }
    SPI_CSb = 1;
    return (status);
}

SD_STATUS erase_pointer()
{
    return write_pointer(1, 0, 0);
}

SD_STATUS read_state(long *status)
{
    SD_STATUS sd_status;
    long sectorPointer;
    int bytePointerHigh, bytePointerLow;
    sd_status = read_pointer(&sectorPointer, &bytePointerHigh, &bytePointerLow);

    *status = (sectorPointer-1) * 512 + bytePointerHigh + bytePointerLow;

    return sd_status;
}

long sizeOfCard()
{
    long SDsize;
    int c_size_mult = gblCSDReg.C_SIZE_MULT+2;
    long c_size = gblCSDReg.CCC_H * 256 + gblCSDReg.CCC_L + 1;

    SDsize = ((2 << c_size_mult) * c_size) * 512;
}